%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     角色进程
%%% @end
%%%-------------------------------------------------------------------
-module(role).

-behaviour(gen_server).

-export([
    newday/1

    ,create/7
    ,stop/2
    ,disconnect/1
    ,save/1

    ,element/2
    ,convert/2

    ,rpc/4
    ,apply/3
]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include("common.hrl").
-include("role_data.hrl").
-include("link.hrl").
-include("role.hrl").
-include("event.hrl").

%% 拦截器
-define(interceptors, [role_send_buffer_ic]).

-define(timer_loop_check, erlang:send_after(timer:seconds(?role_time_loop_check), self(), timer_loop_check)).
-define(timer_check_message_len, erlang:send_after(timer:seconds(60), self(), timer_check_message_len)).
-define(timer_role_save, erlang:send_after(timer:seconds(1200), self(), timer_role_save)).

-ifdef(debug).
-define(print_role_size(_Role_), ?DEBUG("role external_size=~w,byte_size=~w", [erlang:external_size(_Role_), erlang:byte_size(util_type:term_to_bitstring(_Role_))])).
-else.
-define(print_role_size(_Role_), null).
-endif.

-ifdef(debug).
-define(print_rpc_exception(Name, Reason, Cmd, Data),
    {{Y, M, D}, {H, M1, S}} = util_time:datetime(),
    io:format(<<"## error ~w/~w/~w ~w:~w:~w [~ts]~ts[Cmd:~w Data:~w]">>, [Y, M, D, H, M1, S, Name, <<"的角色进程处理命令时出错 ">>, Cmd, Data]),
    util_role:print(Reason),
    io:format("~n")).
-define(print_apply_exception(Name, Mfa, Else),
    {{Y, M, D}, {H, M1, S}} = util_time:datetime(),
    io:format(<<"## error ~w/~w/~w ~w:~w:~w ~ts[~ts]~ts~w~ts">>, [Y, M, D, H, M1, S, <<"角色">>, State#role.name, <<"执行">>, Mfa, <<"时得到错误的返回值格式">>]),
    util_role:print(Else),
    io:format("~n")).
-else.
-define(print_rpc_exception(Name, Reason, Cmd, Data),
    ?ERR("[~s]的角色进程处理命令时出错:~w [Cmd:~w Data:~w]", [Name, Reason, Cmd, Data])).
-define(print_apply_exception(Name, Mfa, Else),
    ?ERR("角色[~s]执行~w时得到错误的返回值格式:~w", [Name, Mfa, Else])).
-endif.


%%%===================================================================
%%% API functions
%%%===================================================================

newday(Pid) ->
    Pid ! newday.


create(Rid, Sid, Account, Link, ExtParam, CliType, IsCowboy) ->
    gen_server:start({global, {role, Rid}}, ?MODULE, [Rid, Sid, Account, Link, ExtParam, CliType, IsCowboy], []).

%% 强制终止角色进程的运行，可选择同步或异步方式
%% 注意: 如果 Msg 为非空则需要等 Msg 发送完成后才有返回，当玩家网络不好时，这个时间可能会比较长
stop(sync, RolePid) ->
    catch gen_server:call(RolePid, stop),
    ok;
stop(async, RolePid) ->
    RolePid ! stop.

disconnect(RolePid) ->
    RolePid ! disconnect.

save(Pid) ->
    Pid ! save.


%% 获取指定角色的某一个或多个属性 -> {ok, [term()]} | {error, Error}
%% {ok, [Name, Status]} = role:element(RolePid, [#role.name, #role.status])
element(RolePid, _ElemPos) when self() =:= RolePid ->
    {error, self_call};
element(RolePid, ElemPos) when is_integer(ElemPos) ->
    case catch gen_server:call(RolePid, {element, [ElemPos]}) of
        {'EXIT', {timeout, _}} -> {error, timeout};
        {'EXIT', Reason} -> {error, Reason};
        EL -> {ok, EL}
    end;
element(RolePid, ElemPos) when is_list(ElemPos) ->
    case catch gen_server:call(RolePid, {element, ElemPos}) of
        {'EXIT', {timeout, _}} -> {error, timeout};
        {'EXIT', Reason} -> {error, Reason};
        EL -> {ok, EL}
    end.

%% 将角色转换为指定的数据类型 -> {ok, record()} | {error, Error}
convert(_Type, RolePid) when self() =:= RolePid ->
    {error, self_call};
convert(Type, RolePid) ->
    ?CALL(RolePid, {convert, Type}).


%% 客户端调用接口(socket 事件处理)
rpc(RolePid, Mod, Cmd, Bin) ->
    RolePid ! {rpc, Mod, Cmd, Bin}.

%% 异步调用
apply(async, RolePid, {M, F, A}) when is_pid(RolePid) ->
    RolePid ! {apply_async, {M, F, A}};
apply(async, Rid, {M, F, A}) ->
    case global:whereis_name({role, Rid}) of
        Pid when is_pid(Pid) -> Pid ! {apply_async, {M, F, A}};
        _ -> false
    end;

%% 异步调用(不在线加入离线事件)
apply(adv_async, RolePid, {M, F, A}) when is_pid(RolePid) ->
    RolePid ! {apply_async, {M, F, A}};
apply(adv_async, Rid, {M, F, A}) ->
    case global:whereis_name({role, Rid}) of
        Pid when is_pid(Pid) -> Pid ! {apply_async, {M, F, A}};
        _ ->
            case db_mnesia:read(role_base, Rid) of
                {ok, [#role_base{}]} -> ok;
                %role_offline_event:add_event(Rid, {M, F, A});
                _ -> false
            end
    end;

apply(sync, Rid, Mfa) when is_integer(Rid) ->
    case global:whereis_name({role, Rid}) of
        Pid when is_pid(Pid) -> ?MODULE:apply(sync, Pid, Mfa);
        _ -> {error, not_found}
    end;
apply(sync, RolePid, _Mfa) when not is_pid(RolePid) ->
    {error, not_pid};
apply(sync, RolePid, Mfa) when self() =:= RolePid ->
    ?ERR("执行 apply 时调用了自身[~w,~w]", [RolePid, Mfa]),
    {error, self_call};
apply(sync, RolePid, {M, F, A}) ->
    ?CALL(RolePid, {apply_sync, {M, F, A}}).


%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

init([Rid, _Sid, Account, Link = #link{conn_pid = ConnPid}, ExtParam, CliType, IsCowboy]) ->
    process_flag(trap_exit, true),

    put(is_cowboy, IsCowboy),
    put(is_role_process, true), %标识下这是一个角色进程
    put(already_handle_stop, false), %是否已经执行过退出处理
    put(role_info, {Account, Rid}), %将角色帐号信息放入进程字典
    put(conn_pid, ConnPid), %缓存连接器进程，以供缓冲区发送时直接调用
    put(sync_counter, 1), %数据同步记数器

    case role_dao:load_role(Rid) of
        {ok, Role = #role{account = Account, label = Label}} ->
            util_math:rand_seed(),
            erlang:link(ConnPid),
            erlang:register(list_to_atom(lists:concat(["role_", Rid])), self()),
            self() ! init,

            Role2 = Role#role{pid = self(), link = Link, plat_info = ExtParam, label = label(CliType, Label)},
            Role3 = role_event:fire(Role2, ?event_role_load),
            ?print_role_size(Role3),
            {ok, Role3};
        {ok, #role{}} ->
            {stop, account_not_match};
        {false, Reason} -> {stop, Reason}
    end.

handle_call(stop, _From, State) ->
    do_stop(State),
    {stop, normal, State};

handle_call({element, Pos}, _From, State) when is_list(Pos) ->
    {reply, lists:reverse(do_element(Pos, State, tuple_size(State), [])), State};

handle_call({convert, to_role}, _From, State) ->
    {reply, {ok, State}, State};

handle_call({convert, Type}, _From, State) ->
    {reply, role_convert:do(Type, State), State};

handle_call({apply_sync, Mfa}, _From, State) ->
    handle_sync_apply(Mfa, State);

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.


handle_info(init, Role) ->
    self() ! gc,

    Role2 = role_event:fire(Role, ?event_role_login),

    role_online_mgr:join(Role2),
    role_dao:login_save_base(Role2),

    ?timer_loop_check,
    ?timer_check_message_len,
    ?timer_role_save,

    {noreply, Role2};

handle_info(gc, State) ->
    garbage_collect(),
    erlang:send_after(timer:seconds(180), self(), gc),
    {noreply, State};

%% 工作循环
handle_info(timer_loop_check, State) ->
    ?timer_loop_check,

    {noreply, role_misc:loop_check(State)};

%% 定时检查链接进程邮箱消息长度
handle_info(timer_check_message_len, State = #role{name = Name, link = #link{socket = Socket, conn_pid = ConnPid}}) ->
    ?timer_check_message_len,

    case process_info(ConnPid, message_queue_len) of
        {_, Len} when is_integer(Len) andalso Len > 500 ->
            Status = process_info(ConnPid, status),
            CurFun = process_info(ConnPid, current_function),
            SockStat = inet:getstat(Socket),

            exit(ConnPid, kill),
            ?ERR("role(~s) kicked out, msg_len(~w) status(~w) cur_fun(~w) sock_stat(~w)", [Name, Len, Status, CurFun, SockStat]),
            ok;
        _ -> ok
    end,
    {noreply, State};

%% 定时保存角色数据
handle_info(timer_role_save, State) ->
    ?timer_role_save,

    role_online_mgr:save_req(State),
    {noreply, State};

%% 定时器消息处理
handle_info({timer, TimerId}, State) ->
    case catch role_timer:handle_timer(State, TimerId) of
        {ok, {M, F, A}, NewState} ->
            handle_async_apply({M, F, A}, NewState);
        {ok} ->
            {noreply, State};
        Other ->
            ?ERR("处理定时器出错, timer_id=~w,reason=~w", [TimerId, Other]),
            {noreply, State}
    end;

handle_info(newday, Role) ->
    Role2 = role_event:fire(Role, ?event_role_newday),
    {noreply, Role2};


handle_info(stop, State) ->
    do_stop(State),
    {stop, normal, State};

handle_info(disconnect, State = #role{account = Account, name = Name}) ->
    ?INFO("[~s,~s]客户端断开连接 relink:~w", [Account, Name, get(relink)]),
    case erase(relink) of
        undefined -> stop(async, self());
        _ -> ok
    end,

    {noreply, State};

handle_info(save, State) ->
    catch role_mgr:save(State),
    {noreply, State};

handle_info({rpc, Mod, Cmd, Data}, State) ->
    handle_rpc({rpc, Mod, Cmd, Data}, State);

handle_info({apply_async, Mfa}, State) ->
    handle_async_apply(Mfa, State);

%% 转发数据到连接进程
handle_info({socket_proxy, Bin}, State = #role{link = #link{conn_pid = ConnPid}}) ->
    util_role:send(ConnPid, Bin),
    {noreply, State};

%% 连接器进程异常退出，协助处理收尾工作
handle_info({'EXIT', Pid, Why}, State = #role{account = Account, name = Name, link = #link{socket = Socket, conn_pid = ConnPid}}) when Pid =:= ConnPid ->
    case Why of
        normal ->
            ?INFO("[~s,~s]的连接器进程[~w]退出，原因:~w", [Account, Name, Pid, Why]);
        _ -> ?ERR("[~s,~s]的连接器进程[~w]异常退出，原因:~w", [Account, Name, Pid, Why])
    end,

    catch gen_tcp:close(Socket),
    self() ! disconnect,

    {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, State) ->
    ?IF(get(already_handle_stop) =:= true, ok, do_stop(State)),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%%===================================================================
%%% Internal functions
%%%===================================================================

label(tester, _) -> ?role_label_tester;
label(_, Label) -> Label.

do_stop(Role = #role{name = Name}) ->
    put(already_handle_stop, true),

    Role2 =
        case role_event:fire(Role, ?event_role_logout) of
            Ns = #role{} -> Ns;
            Other ->
                ?ERR("角色退出时发生错误:name=~s,reason=~w", [Name, Other]),
                Role
        end,

    role_mgr:save(Role2).

%% 获取#role{}中的某些属性
do_element([], _State, _MaxPos, L) -> L;
do_element([H | T], State, MaxPos, L) when H > 0 andalso H =< MaxPos  ->
    do_element(T, State, MaxPos, [erlang:element(H, State) | L]);
do_element([H | T], State, MaxPos, L) ->
    ?ERR("传给role:element/2的参数不正确: ~w", [H]),
    do_element(T, State, MaxPos, L).

%% 通知连接器读取下一条指令
read_next(ConnPid, State) ->
    ConnPid ! read_next,
    {noreply, State}.

%% 处理协议rpc
handle_rpc({rpc, Mod, Cmd, Data}, State = #role{name = Name, link = #link{conn_pid = ConnPid}}) ->
    RpcConfig = role_rpc_conf:get(rpc, State, {Cmd, Data}),
    Rpc = {rpc, Cmd, Data},
    case handle_before(Rpc, RpcConfig, State) of
        false ->
            read_next(ConnPid, State);
        true ->
            case catch Mod:handle(Cmd, Data, State) of
                {ok} ->
                    handle_after_success(Rpc, RpcConfig, State, State),
                    read_next(ConnPid, State);
                {ok, NewState} when is_record(NewState, role) ->
                    NewState2 = handle_after_success(Rpc, RpcConfig, State, NewState),
                    sync(State, NewState2),
                    read_next(ConnPid, NewState2);
                {ok, SendCmd, Reply} when is_integer(SendCmd) ->
                    util_role:pack_send(ConnPid, SendCmd, Reply),
                    handle_after_success(Rpc, RpcConfig, State, State),
                    read_next(ConnPid, State);
                {ok, SendCmd, Reply, NewState} when is_integer(SendCmd) andalso is_record(NewState, role) ->
                    util_role:pack_send(ConnPid, SendCmd, Reply),
                    NewState2 = handle_after_success(Rpc, RpcConfig, State, NewState),
                    sync(State, NewState2),
                    read_next(ConnPid, NewState2);
                {false} ->
                    handle_after_fail(Rpc, RpcConfig, State),
                    read_next(ConnPid, State);
                {false, SendCmd, Reply} when is_integer(SendCmd) ->
                    handle_after_fail(Rpc, RpcConfig, State),
                    util_role:pack_send(ConnPid, SendCmd, Reply),
                    read_next(ConnPid, State);
                {nosync, NewState} ->
                    handle_after_success(Rpc, RpcConfig, State, NewState),
                    read_next(ConnPid, NewState);
                {nosync, Reply, NewState} ->
                    util_role:pack_send(ConnPid, Cmd, Reply),
                    NewState2 = handle_after_success(Rpc, RpcConfig, State, NewState),
                    read_next(ConnPid, NewState2);
                _Reason ->
                    ?print_rpc_exception(Name, _Reason, Cmd, Data),
                    handle_after_fail(Rpc, RpcConfig, State),
                    read_next(ConnPid, State)
            end
    end.

%% 处理同步apply
handle_sync_apply(Mfa, State) ->
    RpcConfig = role_rpc_conf:get(apply, State, Mfa),
    Rpc = {apply, Mfa},
    case handle_before(Rpc, RpcConfig, State) of
        true ->
            case do_mfa(Mfa, State) of
                {ok, Reply} ->
                    handle_after_success(Rpc, RpcConfig, State, State),
                    {reply, Reply, State};
                {ok, Reply, NewState} when is_record(NewState, role) ->
                    NewState2 = handle_after_success(Rpc, RpcConfig, State, NewState),
                    {reply, Reply, NewState2};
                {false, Reply} ->
                    handle_after_fail(Rpc, RpcConfig, State),
                    {reply, Reply, State};
                Else ->
                    ?print_apply_exception(State#role.name, Mfa, Else),
                    handle_after_fail(Rpc, RpcConfig, State),
                    {reply, Else, State}
            end;
        false ->
            {reply, error, State}
    end.

%% 处理异步apply
handle_async_apply(Mfa, State) ->
    RpcConfig = role_rpc_conf:get(apply, State, Mfa),
    Rpc = {apply, Mfa},
    case handle_before(Rpc, RpcConfig, State) of
        true ->
            case do_mfa(Mfa, State) of
                {ok} ->
                    handle_after_success(Rpc, RpcConfig, State, State),
                    {noreply, State};
                {ok, NewState} when is_record(NewState, role) ->
                    NewState2 = handle_after_success(Rpc, RpcConfig, State, NewState),
                    {noreply, NewState2};
                {false, _Reason} ->
                    handle_after_fail(Rpc, RpcConfig, State),
                    {noreply, State};
                Else ->
                    handle_after_fail(Rpc, RpcConfig, State),
                    ?print_apply_exception(State#role.name, Mfa, Else),
                    {noreply, State}
            end;
        false ->
            {noreply, State}
    end.

do_mfa({M, F, A}, State) ->
    catch erlang:apply(M, F, [State | A]).

%% 执行rpc前的检测
handle_before(_Rpc, undefined, _State) ->
    true;
handle_before(Rpc, RpcCfg, State = #role{name = Name}) ->
    case catch do_handle_before(RpcCfg, Rpc, State, ?interceptors, []) of
        true ->
            true;
        false ->
            false;
        Other ->
            ?ERR("[~s]执行[~w]时出错: ~w", [Name, Rpc, Other]),
            false
    end.

do_handle_before(_RpcCfg, _Rpc, _State, [], _Back) ->
    true;
do_handle_before(RpcCfg, Rpc, State, [Aspect | T], Back) ->
    case Aspect:rpc_before(RpcCfg, Rpc, State) of
        false ->
            do_handle_after_fail(RpcCfg, Rpc, State, Back),
            false;
        true ->
            do_handle_before(RpcCfg, Rpc, State, T, [Aspect | Back])
    end.

%% 执行rpc，apply后的流程操作
handle_after_success(_Rpc, undefined, _State, NewState) ->
    NewState;
handle_after_success(Rpc, RpcCfg, State, NewState = #role{name = Name}) ->
    case catch do_handle_after_success(RpcCfg, Rpc, State, NewState, lists:reverse(?interceptors)) of
        Ns2 = #role{} ->
            Ns2;
        Other ->
            ?ERR("[~s]执行[~w]时出错: ~w", [Name, Rpc, Other]),
            NewState
    end.

do_handle_after_success(_RpcCfg, _Rpc, _State, NewState, []) ->
    NewState;
do_handle_after_success(RpcCfg, Rpc, State, NewState, [Aspect | T]) ->
    Ns2 = Aspect:rpc_after_success(RpcCfg, Rpc, State, NewState),
    do_handle_after_success(RpcCfg, Rpc, State, Ns2, T).

%% 执行rpc，apply后的流程操作
handle_after_fail(_Rpc, undefined, _State) ->
    ignore;
handle_after_fail(Rpc, RpcCfg, State = #role{name = Name}) ->
    case catch do_handle_after_fail(RpcCfg, Rpc, State, lists:reverse(?interceptors)) of
        ignore ->
            ignore;
        Other ->
            ?ERR("[~s]执行[~w]时出错: ~w", [Name, Rpc, Other]),
            ignore
    end.

do_handle_after_fail(_RpcCfg, _Rpc, _State, []) ->
    ignore;
do_handle_after_fail(RpcCfg, Rpc, State, [Aspect | T]) ->
    Aspect:rpc_after_fail(RpcCfg, Rpc, State),
    do_handle_after_fail(RpcCfg, Rpc, State, T).

%% 同步处理
sync(#role{assets = #assets{gold = OldGold}}, Role = #role{assets = #assets{gold = NewGold}}) when OldGold =/= NewGold ->
    role_mgr:save(Role),
    put(sync_counter, 1);
sync(_OldRole, Role = #role{lev = Lev}) ->
    N = get(sync_counter),
    case N > 30 orelse (N > 15 andalso Lev > 50)  of
        true ->
            role_online_mgr:save_req(Role),
            put(sync_counter, 1);
        _ ->
            put(sync_counter, N + 1)
    end.
